
#ifndef _TTGEXT_BINDINGS_CUDA_BINDINGS_
#define _TTGEXT_BINDINGS_CUDA_BINDINGS_
#include <Common/Definitions.h>

#include <stdlib.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <ttg/errors.h>
#include <ttg/math.h>
#include <ttg/structures.h>
#include <ttg/pipeline.h>

#pragma comment(lib, "cuda.lib")
#pragma comment(lib, "cudart.lib")

#ifndef __CUDACC__
namespace ttg_ext_internal
{
	namespace bindings
	{
		//Invokes onBegin()/onEnd().
		template <class T>
		class OnBeginOnEndInvoker :public ttg::structures::InvokableObject
		{
			private:
				T *binding;
				bool on_begin;
				ttg::structures::UniversalStack *this_buffer;
				ttg::structures::UniversalStack *next_buffer;
				ttg_internal::pipeline::DataBuffer *buffer;
			public:
				//It's too internal. Do not try to modify it!
				inline OnBeginOnEndInvoker(T *binding, bool on_begin,
										   ttg_internal::pipeline::DataBuffer *buffer,
										   ttg::structures::UniversalStack *this_buffer,
										   ttg::structures::UniversalStack *next_buffer)
				{
					this->binding = binding;
					this->on_begin = on_begin;
					this->buffer = buffer;
					this->this_buffer = this_buffer;
					this->next_buffer = next_buffer;
				}
				virtual void invoke()
				{
					if (on_begin)
						binding->onBegin();
					else
						binding->onEnd();
					*this_buffer = buffer->getThisBuffer();
					buffer->getThisBuffer().clear();
					*next_buffer = buffer->getNextBuffer();
					buffer->getNextBuffer().clear();
				}
		};

		//Invokes process().
		template <class BindingType, class DataType>
		class ProcessInvoker :public ttg::structures::InvokableObject
		{
			private:
				BindingType *binding;
				DataType data;
				ttg::structures::UniversalStack *this_buffer;
				ttg::structures::UniversalStack *next_buffer;
				ttg_internal::pipeline::DataBuffer *buffer;
			public:
				inline ProcessInvoker(BindingType *binding, DataType data,
									  ttg_internal::pipeline::DataBuffer *buffer,
									  ttg::structures::UniversalStack *this_buffer,
									  ttg::structures::UniversalStack *next_buffer)
				{
					this->binding = binding;
					this->data = data;
					this->buffer = buffer;
					this->this_buffer = this_buffer;
					this->next_buffer = next_buffer;
				}
				virtual void invoke()
				{
					binding->process(data);
					*this_buffer = buffer->getThisBuffer();
					buffer->getThisBuffer().clear();
					*next_buffer = buffer->getNextBuffer();
					buffer->getNextBuffer().clear();
				}
		};
	}
}
#endif

namespace ttg_ext
{
	namespace bindings
	{
		//Version of binding for NVidia CUDA. Contains some cutil-like routines.
		template <class InType, class OutType>
#ifdef __CUDACC__
		class cudaBinding
		{
			private:
				volatile ttg::math::uint64 stub1;
				volatile ttg::math::uint64 stub2;
				volatile void *stub3;
			public:
				//Stub for nvcc.
				template <class T>
				inline void sendNext(T data)
				{
					throw ttg::Error(ttg::Error::FATAL,
						"sendNext()/sendThis() cannot be called from *.cu file (due to nvcc incompatibility)");
				}

				//Stub for nvcc.
				template <class T>
				inline void sendThis(T data)
				{
					throw ttg::Error(ttg::Error::FATAL,
						"sendNext()/sendThis() cannot be called from *.cu file (due to nvcc incompatibility)");
				}
#else
		class cudaBinding :public ttg::pipeline::Binding<InType, OutType>
		{
			protected:
				//Invokes onBegin() in CUDA-thread.
				virtual void onBegin(ttg::pipeline::ContextID ID)
				{
					if (ttg::devices::GPU::CUDA::getRef()->isAvailable())
					{
						//Invoking onBegin()/onEnd().
						ttg::devices::GPU::CUDA::getRef()->perform(
							new ttg_ext_internal::bindings::OnBeginOnEndInvoker<ttg::pipeline::Binding<InType,
																									   OutType>>
									(this,
									 true,
									 &getOwner()->getIContext().getDataBuffer(),
									 &getOwner()->getIContext().getDataBuffer().getThisBuffer(),
									 &getOwner()->getIContext().getDataBuffer().getNextBuffer()));
					}
				}
				//Invokes process() in CUDA-thread.
				virtual void process(ttg::pipeline::ContextID ID, InType data)
				{
					if (ttg::devices::GPU::CUDA::getRef()->isAvailable())
					{
						//Invoking process.
						ttg::devices::GPU::CUDA::getRef()->perform(
							new ttg_ext_internal::bindings::ProcessInvoker
								<ttg::pipeline::Binding<InType, OutType>, InType>
								(this, data,
								 &getOwner()->getIContext().getDataBuffer(),
								 &getOwner()->getIContext().getDataBuffer().getThisBuffer(),
								 &getOwner()->getIContext().getDataBuffer().getNextBuffer()));
					}
				}
				//Invokes onEnd() in CUDA-thread.
				virtual void onEnd(ttg::pipeline::ContextID ID)
				{
					if (ttg::devices::GPU::CUDA::getRef()->isAvailable())
					{
						//Invoking onBegin()/onEnd().
						ttg::devices::GPU::CUDA::getRef()->perform(
							new ttg_ext_internal::bindings::OnBeginOnEndInvoker<ttg::pipeline::Binding<InType,
																									   OutType>>
									(this,
									 false,
									 &getOwner()->getIContext().getDataBuffer(),
									 &getOwner()->getIContext().getDataBuffer().getThisBuffer(),
									 &getOwner()->getIContext().getDataBuffer().getNextBuffer()));
					}
				}
#endif
			public:
				//Checks error code and [optionally] generates exception.
				static inline void safeCall(CUresult call,
											const char *file = NULL,
											int line = -1)
				{
					if (call != CUDA_SUCCESS)
					{
						char buf[4 * 1024];
						size_t offset = sprintf(buf, "CUDA error has been detected (%s)",
													 cudaGetErrorString(cudaGetLastError()));
#ifdef TTG_DEBUG
						if (file != NULL)
							offset += sprintf(buf + offset, " at %s", file);
						if (line >= 0)
							offset += sprintf(buf + offset, ":%d", line);
#endif
						throw ttg::Error(ttg::Error::FATAL, buf);
					} 
				}
				//Checks error code and [optionally] generates exception.
				static inline void safeCall(cudaError_t call,
											const char *file = NULL,
											int line = -1)
				{
					if (call != cudaSuccess)
					{
						char buf[4 * 1024];
						size_t offset = sprintf(buf, "CUDA error has been detected (%s)",
													 cudaGetErrorString(cudaGetLastError()));
#ifdef TTG_DEBUG
						if (file != NULL)
							offset += sprintf(buf + offset, " at %s", file);
						if (line >= 0)
							offset += sprintf(buf + offset, ":%d", line);
#endif
						throw ttg::Error(ttg::Error::FATAL, buf);
					} 
				}
				//Checks last CUDA operation. In case of error throws exception.
				static inline void checkError(const char *error_message,
											  const char *file = NULL,
											  int line = -1)
				{
					cudaError_t err = cudaGetLastError();
					if (err != cudaSuccess)
					{
						char buf[4 * 1024];
						size_t offset = sprintf(buf, "CUDA error has been detected: %s (%s)",
												  error_message, cudaGetErrorString(err));
#ifdef TTG_DEBUG
						if (file != NULL)
							offset += sprintf(buf + offset, " at %s", file);
						if (line >= 0)
							offset += sprintf(buf + offset, ":%d", line);
#endif
						throw ttg::Error(ttg::Error::FATAL, buf);
					}
					err = cudaThreadSynchronize();
					if (err != cudaSuccess)
					{
						char buf[4 * 1024];
						size_t offset = sprintf(buf, "CUDA error has been detected: %s (%s)",
												  error_message, cudaGetErrorString(err));
#ifdef TTG_DEBUG
						if (file != NULL)
							offset += sprintf(buf + offset, " at %s", file);
						if (line >= 0)
							offset += sprintf(buf + offset, ":%d", line);
#endif
						throw ttg::Error(ttg::Error::FATAL, buf);
					}
				}
#ifndef __CUDACC__
			public:
				inline cudaBinding()
					:Binding(ttg::devices::GPU::CUDA::getType())
				{ /*nothing*/ }
#endif
		};

#define SAFE_CALL(err_code) ttg_ext::bindings::cudaBinding<bool, bool>::safeCall(err_code, __FILE__, __LINE__);
#define CHECK_ERROR(desc) ttg_ext::bindings::cudaBinding<bool, bool>::checkError(desc, __FILE__, __LINE__);
	}
}

#endif
